using System;
using System.Collections.Generic;
using System.Text;
using SP.Parameter;

namespace SP.Reactor
{
    public class FxReactor : AbstractReactor
    {
        public FxReactor()
            : base()
        {
        }

        public override string Name
        {
            get { return "FxReactor"; }
        }

        public override string[] CodeFiles
        {
            get { return new string[] {".fx"}; }
        }

        public override void Generate(Shader shader, string codeFile, StringBuilder b)
        {
            mShader = shader;

            CodePreprocessor(b);
            
            CodeFxHeader(b);

            CodeParameter(b);

            CodeConstant(b);

            CodeVertHeader(b);
            CodeVertBody(b);
            CodeVertFooter(b);

            CodeFragHeader(b);
            CodeFragVarying(b);
            CodeFragBody(b);
            CodeFragFooter(b);

            CodeFxFooter(b);
        }

        private void CodeFxHeader(StringBuilder b)
        {
            b.AppendLine("#define DCC_MAX");
            b.AppendLine("string ParamID = \"0x000001\";");
            b.AppendLine("//--------//--------//--------//--------//--------");
            b.AppendLine("//Fx Header");

            b.AppendLine("typedef float4x4 mat4;");
            b.AppendLine("typedef float3x3 mat3;");
            b.AppendLine("typedef float4 vec4;");
            b.AppendLine("typedef float3 vec3;");
            b.AppendLine("typedef float2 vec2;");

            b.AppendLine("vec4 xform(mat4 m, vec4 v) { return mul(v, m); }");
            b.AppendLine("vec3 xform(mat3 m, vec3 v) { return mul(v, m); }");

            b.AppendLine("struct VpIn");
            b.AppendLine("{");
            b.AppendLine("    vec4 Pos          : POSITION;");
            b.AppendLine("    vec3 Norm         : NORMAL;");
            b.AppendLine("    vec3 Tang         : TANGENT;");
            b.AppendLine("    vec2 UV0          : TEXCOORD0;");
            b.AppendLine("    vec2 UV1          : TEXCOORD1;");
            b.AppendLine("    vec2 UV2          : TEXCOORD2;");
            b.AppendLine("    vec4 Color        : COLOR0;");
            b.AppendLine("};");

            b.AppendLine("struct VpOut");
            b.AppendLine("{");
            b.AppendLine("    vec4 Pos          : POSITION;");
            b.AppendLine("    vec4 Color        : COLOR0;");
            b.AppendLine("    vec2 UV0          : TEXCOORD0;");
            b.AppendLine("    vec2 UV1          : TEXCOORD1;");
            b.AppendLine("    vec2 UV2          : TEXCOORD2;");
            b.AppendLine("    vec3 V_Vs         : TEXCOORD3;");
            b.AppendLine("    vec3 T_Vs         : TEXCOORD4;");
            b.AppendLine("    vec3 B_Vs         : TEXCOORD5;");
            b.AppendLine("    vec3 N_Vs         : TEXCOORD6;");
            b.AppendLine("    vec4 Pos_Cs       : TEXCOORD7;");
            b.AppendLine("};");

            b.AppendLine("struct VpPipeline");
            b.AppendLine("{");
            b.AppendLine("    vec4 Pos_Ms;");
            b.AppendLine("    vec3 T_Ms;");
            b.AppendLine("    vec3 B_Ms;");
            b.AppendLine("    vec3 N_Ms;");
            b.AppendLine("    vec4 Color;");
            b.AppendLine("    vec2 UV0;");
            b.AppendLine("    vec2 UV1;");
            b.AppendLine("    vec2 UV2;");
            b.AppendLine("};");

            b.AppendLine("struct FpIn");
            b.AppendLine("{");
            b.AppendLine("    vec4 Color        : COLOR0;");
            b.AppendLine("    vec2 UV0          : TEXCOORD0;");
            b.AppendLine("    vec2 UV1          : TEXCOORD1;");
            b.AppendLine("    vec2 UV2          : TEXCOORD2;");
            b.AppendLine("    vec3 V_Vs         : TEXCOORD3;");
            b.AppendLine("    vec3 T_Vs         : TEXCOORD4;");
            b.AppendLine("    vec3 B_Vs         : TEXCOORD5;");
            b.AppendLine("    vec3 N_Vs         : TEXCOORD6;");
            b.AppendLine("    vec4 Pos_Cs       : TEXCOORD7;");
            b.AppendLine("    float Face        : FACE;");
            b.AppendLine("};");

            b.AppendLine("struct FpOut");
            b.AppendLine("{");
            b.AppendLine("    vec4 C0           : COLOR0;");
            b.AppendLine("};");

            b.AppendLine("struct FpPipeline");
            b.AppendLine("{");
            b.AppendLine("    vec2  Texcoord[3];");
            b.AppendLine("    vec3  ViewDir;");
            b.AppendLine("    vec3  Normal;");
            b.AppendLine("    vec3  Albedo;");
            b.AppendLine("    vec3  Gloss;");
            b.AppendLine("    float Opacity;");
            b.AppendLine("    vec3  Illumination;");
            b.AppendLine("    vec3  Reflection;");
            b.AppendLine("    vec3  FragColor;");
            b.AppendLine("};");
        }

        private void CodePreprocessor(StringBuilder b)
        {
            b.AppendLine("//--------//--------//--------//--------//--------");
            b.AppendLine("// Preprocessors");

            foreach (IFusion f in mShader.Fusions)
            {
                if (null != f)
                {
                    foreach (Preprocessor p in f.Preprocessors)
                    {
                        //string line = "#define " + p.Name + " " + p.Value.ToString();
                        string line = String.Format("#define {0} {1}", p.Name, p.Value);
                        b.AppendLine(line);
                    }
                }
            }
        }

        private void CodeParameter(StringBuilder b)
        {
            b.AppendLine("//--------//--------//--------//--------//--------");
            b.AppendLine("// Parameters");

            foreach (IFusion f in mShader.Fusions)
            {
                if (null != f)
                {
                    foreach (IParameter p in f.Parameters)
                    {
                        if (p.GetType() == typeof(Texture2dParamter))
                        {
                            Texture2dParamter ptex2d = p as Texture2dParamter;
                            b.AppendLine(String.Format
                                ("texture {0} < string UIName=\"{1}\"; string ResourceName=\"{2}\"; >;"
                                , ptex2d.Name
                                , ptex2d.Name
                                , ptex2d.Path
                                ));

                            b.AppendLine(String.Format
                                ("sampler2D {0} = sampler_state{{ Texture = <{1}>; MinFilter = {2}; MagFilter = {3}; MipFilter = {4}; AddressU = {5}; AddressV = {6}; SRGBTexture = {7}; }};"
                                , ptex2d.SamplerName
                                , ptex2d.Name
                                , GetTextureFilterName(ptex2d.MinFilter)
                                , GetTextureFilterName(ptex2d.MagFilter)
                                , GetTextureFilterName(ptex2d.MipFilter)
                                , GetTextureAddressMode(ptex2d.UAddressMode)
                                , GetTextureAddressMode(ptex2d.VAddressMode)
                                , ptex2d.SRGBTexture ? "true" : "false"
                                ));
                        }
                        else if (p.GetType() == typeof(TextureCubeParamter))
                        {
                            TextureCubeParamter ptexCube = p as TextureCubeParamter;
                            b.AppendLine(String.Format
                                ("texture {0} < string UIName=\"{1}\"; string ResourceName=\"{2}\"; >;"
                                , ptexCube.Name
                                , ptexCube.Name
                                , ptexCube.Path
                                ));

                            b.AppendLine(String.Format
                                ("samplerCUBE {0} = sampler_state{{ Texture = <{1}>; MinFilter = {2}; MagFilter = {3}; MipFilter = {4}; AddressU = {5}; AddressV = {6}; AddressW = {7}; SRGBTexture = {8}; }};"
                                , ptexCube.SamplerName
                                , ptexCube.Name
                                , GetTextureFilterName(ptexCube.MinFilter)
                                , GetTextureFilterName(ptexCube.MagFilter)
                                , GetTextureFilterName(ptexCube.MipFilter)
                                , GetTextureAddressMode(ptexCube.UAddressMode)
                                , GetTextureAddressMode(ptexCube.VAddressMode)
                                , GetTextureAddressMode(ptexCube.WAddressMode)
                                , ptexCube.SRGBTexture ? "true" : "false"
                                ));
                        }
                        else if (p.GetType() == typeof(ScalarParameter))
                        {
                            b.AppendLine(String.Format("float {0} = {1};", p.Name, p.Value));
                        }
                        else if (p.GetType() == typeof(Vec2Parameter))
                        {
                            b.AppendLine(String.Format("vec2 {0} = vec2({1});", p.Name, p.Value));
                        }
                        else if (p.GetType() == typeof(Vec3Parameter))
                        {
                            b.AppendLine(String.Format("vec3 {0} = vec3({1});", p.Name, p.Value));
                        }
                        else if (p.GetType() == typeof(Vec4Parameter))
                        {
                            b.AppendLine(String.Format("vec4 {0} = vec4({1});", p.Name, p.Value));
                        }
                        else if (p.GetType() == typeof(ColorParameter))
                        {
                            b.AppendLine(String.Format("vec4 {0} = vec4({1});", p.Name, p.Value));
                        }
                    }
                }
            }
        }

        private void CodeConstant(StringBuilder b)
        {
            b.AppendLine("//--------//--------//--------//--------//--------");
            b.AppendLine("// Constants");
            b.AppendLine("mat4 cWorldMtx            : WORLD;");
            b.AppendLine("mat4 cWorldViewMtx        : WORLDVIEW;");
            b.AppendLine("mat4 cWorldViewProjMtx    : WORLDVIEWPROJECTION;");
            b.AppendLine("mat4 cViewMtx             : VIEW;");
            b.AppendLine("mat4 cViewIMtx            : VIEWI;");
            b.AppendLine("mat4 cProjMtx             : PROJECTION;");
            b.AppendLine("float cTime               : TIME;");
        }

        #region Vertex Program
        private void CodeVertHeader(StringBuilder b)
        {
            b.AppendLine("//--------//--------//--------//--------//--------");
            b.AppendLine("// Vertex program");
            b.AppendLine("void Vert(VpIn input, out VpOut output)");
            b.AppendLine("{");
            b.AppendLine("VpPipeline pipeline;");
        }

        private void CodeVertFooter(StringBuilder b)
        {
            b.AppendLine("}");
        }

        private void CodeVertBody(StringBuilder b)
        {
            b.AppendLine("// Vertex pipeline init");
            b.AppendLine("pipeline.Pos_Ms = input.Pos.xyzw;");
            b.AppendLine("pipeline.N_Ms = input.Norm.xyz;");
            b.AppendLine("pipeline.T_Ms = input.Tang.xyz;");
            b.AppendLine("pipeline.B_Ms = cross(pipeline.T_Ms, pipeline.N_Ms);");
            b.AppendLine("pipeline.Color = input.Color;");
            b.AppendLine("pipeline.UV0 = input.UV0;");
            b.AppendLine("pipeline.UV1 = input.UV1;");
            b.AppendLine("pipeline.UV2 = input.UV2;");

            // vertex fusions
            foreach (IFusion f in Utils.GetEnabledFusionsByClass(mShader, Stage.Vertex))
                AppendFeatureCode(f, b);

            b.AppendLine("// Vertex pipeline output");
            b.AppendLine("output.Pos  = xform(cWorldViewProjMtx, pipeline.Pos_Ms);");
            b.AppendLine("output.Pos_Cs = output.Pos * vec4(1, -1, 1, 1);");
            b.AppendLine("output.V_Vs = xform(cWorldViewMtx, pipeline.Pos_Ms).xyz;");
            b.AppendLine("output.N_Vs = xform((mat3)cWorldViewMtx, pipeline.N_Ms).xyz;");
            b.AppendLine("output.T_Vs = xform((mat3)cWorldViewMtx, pipeline.T_Ms).xyz;");
            b.AppendLine("output.B_Vs = xform((mat3)cWorldViewMtx, pipeline.B_Ms).xyz;");

            b.AppendLine("output.Color = pipeline.Color;");

            b.AppendLine("output.UV0 = pipeline.UV0;");
            b.AppendLine("output.UV1 = pipeline.UV1;");
            b.AppendLine("output.UV2 = pipeline.UV2;");
        }

        #endregion // Vertex Program

        #region Fragment Program
        private void CodeFragHeader(StringBuilder b)
        {
            b.AppendLine("//--------//--------//--------//--------//--------");
            b.AppendLine("// Fragment program");
            b.AppendLine("void Frag(FpIn input, out FpOut output)");
            b.AppendLine("{");
        }

        private void CodeFragVarying(StringBuilder b)
        {
            b.AppendLine("// Fragment pipeline init");
            b.AppendLine("FpPipeline pipeline;");
            b.AppendLine("pipeline.Texcoord[0] = input.UV0;");
            b.AppendLine("pipeline.Texcoord[1] = input.UV1;");
            b.AppendLine("pipeline.Texcoord[2] = input.UV2;");

            b.AppendLine("pipeline.ViewDir = normalize(input.V_Vs);");
            b.AppendLine("pipeline.Normal = normalize(input.N_Vs);");
            b.AppendLine("pipeline.Albedo = vec3(1,1,1);");
            b.AppendLine("pipeline.Gloss = vec3(0,0,0);");
            b.AppendLine("pipeline.Opacity = 1;");
            b.AppendLine("pipeline.Illumination = vec3(0,0,0);");
            b.AppendLine("pipeline.Reflection = vec3(0,0,0);");
            b.AppendLine("pipeline.FragColor = vec3(0,0,0);");
        }

        private void CodeFragBody(StringBuilder b)
        {
            // parallax
            foreach (IFusion f in Utils.GetEnabledFusionsByClass(mShader, Stage.Parallex))
                AppendFeatureCode(f, b);

            // opacity
            foreach (IFusion f in Utils.GetEnabledFusionsByClass(mShader, Stage.Opacity))
                AppendFeatureCode(f, b);

            // albedo & gloss
            foreach (IFusion f in Utils.GetEnabledFusionsByClass(mShader, Stage.AlbedoGloss))
                AppendFeatureCode(f, b);

            // illumination
            foreach (IFusion f in Utils.GetEnabledFusionsByClass(mShader, Stage.Illumination))
                AppendFeatureCode(f, b);

            // set illumination to 1.0 if no illumination-features exists
            if ( 0 == Utils.GetEnabledFusionsByClass(mShader, Stage.Illumination).Count )
                b.AppendLine("pipeline.Illumination = vec3(1,1,1);");

            // reflection
            foreach (IFusion f in Utils.GetEnabledFusionsByClass(mShader, Stage.Relfection))
                AppendFeatureCode(f, b);

            // frag color
            b.AppendLine("pipeline.FragColor = pipeline.Albedo * pipeline.Illumination + pipeline.Reflection * pipeline.Gloss;");

            // override
            foreach (IFusion f in Utils.GetEnabledFusionsByClass(mShader, Stage.Override))
                AppendFeatureCode(f, b);
        }

        private void CodeFragFooter(StringBuilder b)
        {
            b.AppendLine("output.C0.rgb = pipeline.FragColor;");
            b.AppendLine("output.C0.a = pipeline.Opacity;");
            b.AppendLine("}");
        }

        #endregion // Fragment Program

        private void CodeFxFooter(StringBuilder b)
        {
            b.AppendLine("//--------//--------//--------//--------//--------");
            b.AppendLine("// FxFooter");
            b.AppendLine("technique Default");
            b.AppendLine("{");
            b.AppendLine("pass Pass0");
            b.AppendLine("{");

            if(mShader.DepthTest)
                b.AppendLine("ZEnable = true;");
            else
                b.AppendLine("ZEnable = false;");

            if (mShader.Transparent)
            {
                b.AppendLine("ZWriteEnable = false;");
                b.AppendLine("AlphaBlendEnable = true;");
                b.AppendLine("SrcBlend = SRC_ALPHA;");
                b.AppendLine("DestBlend = INV_SRC_ALPHA;");
            }
            else
            {
                b.AppendLine("ZWriteEnable = true;");
                b.AppendLine("AlphaBlendEnable = false;");
            }

            if (mShader.TwoSided)
            {
                b.AppendLine("CullMode = None;");
            }
            else
            {
                b.AppendLine("CullMode = CW;");
            }

            b.AppendLine("SRGBWriteEnable = true;");
            b.AppendLine("VertexShader = compile vs_3_0 Vert();");
            b.AppendLine("PixelShader = compile ps_3_0 Frag();");
            b.AppendLine("}");
            b.AppendLine("}");
        }
    }
}
